#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

'''
PDA UI, Personal Digital Assitance

As tkinter is so hard to use, avoid complicate UI widgets, use only the most
simple ones.

Haiyu Zhang (kevin.misc.10@gmail.com)
2012-07-08
'''

import os
import sys
import logging
import datetime
import socket
import threading
import time
from itertools import cycle
import tkFont
from Tkinter import *
import Queue

from unittest import TestCase
from unittest import TestSuite
from unittest import TextTestRunner

import tconfig

UI_DATA_SERVER_PORT = 8900
UI_CMD_SERVER_PORT =  8899
MAX_CONNECT = 1

# May configure logging at module level
#logging.getLogger(__name__).addHandler(logging.NullHandler())

'''CLI'''
class CLIList(object):
    def __init__(self):
        object.__init__(self)

        self.texts = []
        pass

    def show(self):
        for i in range(len(self.texts)):
            print('{0} - {1}'.format(i, self.texts[i]))
        pass

    def append(self, text):
        pass

class CLIPage(object):
    def __init__(self):
        object.__init__(self)

        self.clilist = []

        pass

    def show(self):
        print('{0}'.format(self.__class__.__name_))
        pass

'''GUI'''
class HeathUI(Tk):
    MENU_WIDTH    = 4
    MENU_HEIGHT   = 1
    def __init__(self):
        Tk.__init__(self, None)

        self.logger = logging.getLogger('{0}.{1}'.format(self.__module__,
                                        self.__class__.__name__))
        self.logger.propagate = False
        formatter = logging.Formatter(fmt=tconfig.LOG_FORMAT0,
                                      datefmt=tconfig.LOG_TIME_FORMAT)
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        ch.setFormatter(formatter)
        self.logger.addHandler(ch)

        self.logger.setLevel(level=logging.DEBUG)

        self.logger.info('{0:<15}: Created'.format(
                         self.__class__.__name__.upper()))

        self.geometry('1000x660')

        self.build_fonts()
        self.build_colors()

        self.build_main_window()

        self.build_toplevel()

        self.server_cmd_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        #self.server_cmd_sock.set_reuse_addr()
        #self.server_data_sock.set_reuse_addr()
        try:
            self.server_cmd_sock.bind(('', UI_CMD_SERVER_PORT))
            self.server_cmd_sock.listen(MAX_CONNECT)
            self.server_data_sock.bind(('', UI_DATA_SERVER_PORT))
            self.server_data_sock.listen(MAX_CONNECT)
        except socket.error, e:
            self.logger.critical('socket.error: %s' % e)
            return

        self.server_run = True
        self.server_threads = []
        th = threading.Thread(target=self.serve_cmd_forever)
        th.setName(self.serve_cmd_forever.__name__)
        th.daemon = True
        th.start()

        th = threading.Thread(target=self.serve_data_forever)
        th.setName(self.serve_data_forever.__name__)
        th.daemon = True
        th.start()

        self.server_threads.append(th)

        self.cmdq = Queue.Queue()
        pass

    def build_toplevel(self):
        '''Build Toplevel windows, i.e. popups'''
        self.window_add_hostname = Toplevel(self)
        self.window_add_hostname.protocol("WM_DELETE_WINDOW", self.hide_top)
        self.window_add_hostname.withdraw()

    def hide_top(self):
        self.window_add_hostname.withdraw()
        pass

    def show_top(self):
        self.window_add_hostname.deiconify()
        pass

    def build_fonts(self):
        self.tkfonts = {}

        self.tkfonts["DejaVu Sans"] = tkFont.Font(
            family="DejaVu Sans Condensed", size=12, weight="normal",
            slant="roman")
        self.tkfonts["Courier New"] = tkFont.Font(
            family="Courier New", size=12, weight="normal",
            slant="roman")

        self.tkfont_dejavu_10 = tkFont.Font(
            family="DejaVu Sans Condensed", size=10, weight="normal",
            slant="roman")
        self.tkfont_dejavu_12 = tkFont.Font(
            family="DejaVu Sans Condensed", size=12, weight="normal",
            slant="roman")
        self.tkfont_courier_8 = tkFont.Font(
            family="Courier New", size=8, weight="normal",
            slant="roman")
        self.tkfont_courier_10 = tkFont.Font(
            family="Courier New", size=10, weight="normal",
            slant="roman")
        self.tkfont_courier_12 = tkFont.Font(
            family="Courier New", size=12, weight="normal",
            slant="roman")

    def build_colors(self):
        self.tkcolors = {}
        # RGB
        self.tkcolors["red"] = "#FF0000"
        self.tkcolor_red = self.tkcolors["red"]
        self.tkcolors["green"] = "#00FF00"
        self.tkcolor_green = self.tkcolors["green"]
        self.tkcolors["blue"] = "#0000FF"
        self.tkcolor_blue = self.tkcolors["blue"]
        self.tkcolors["black"] = "#000000"
        self.tkcolor_black = self.tkcolors["black"]
        self.tkcolors["white"] = "#FFFFFF"
        self.tkcolor_white = self.tkcolors["white"]

        #self.color_menubar = self.tkcolor_green
        #self.color_toolbar = self.tkcolor_blue
        #self.color_statusbar = self.tkcolor_black
        #self.color_console = "#00FF00"
        self.color_menubar = self.tkcolor_green
        self.color_toolbar = self.tkcolor_blue
        self.color_statusbar = self.tkcolor_black
        self.color_console = "#00FF00"
        pass

    def build_main_window(self):
        '''Create Telnet tool view by default
        '''
        top = self.winfo_toplevel()         # top-level window of this frame
        # CAUTION
        # Have to forbidden UP key propagated to Text widget from class level
        #top.bind_class("Text", "<Up>", lambda e: None)
        #top.rowconfigure(0, weight=1)
        #top.columnconfigure(0, weight=1)

        #self.rowconfigure(2, weight=1)
        #self.columnconfigure(0, weight=1)

        self.tkvars = {}
        self.tkgrid_row = 0
        self.tkgrid_column = 0

        self.console0 = Text(self, bg=self.tkcolor_black, fg=self.tkcolor_white,
                             insertbackground=self.tkcolor_green,
                             insertborderwidth=4)
        self.console0.grid(row=0, column=0, columnspan=2, sticky='wsen')

        #self.build_menubar()
        pass

    def build_menubar(self):
        frame = Frame(self, bg=self.color_menubar)
        #frame.config(padx=5)
        #frame.config(pady=5)

        row = 0
        column = 0
        # Menu: File
        self.menubutton_file = Menubutton(frame, font=self.tkfont_dejavu_10,
                                          text="File", relief=RAISED,
                                          width=HeathUI.MENU_WIDTH,
                                          height=HeathUI.MENU_HEIGHT)
        #self.menubutton_file.grid()
        self.menubutton_file.menu = Menu(self.menubutton_file, tearoff=0)
        self.menubutton_file["menu"] = self.menubutton_file.menu

        self.menubutton_file.menu.add_separator()

        self.tkvars["var_current_view"] = IntVar()
        self.menubutton_file.menu.add_radiobutton(label="Telnet View",
            variable=self.tkvars["var_current_view"])
        self.menubutton_file.menu.add_radiobutton(label="TFTP View",
            variable=self.tkvars["var_current_view"])
        self.menubutton_file.menu.add_separator()

        self.menubutton_file.menu.add_command(accelerator="s", label="Save",
                                              underline=0,
                                              command=self.test_console_frame)
        self.menubutton_file.menu.add_command(accelerator="x", label="Exit",
                                              underline=0,
                                              command=self.cmd_exit)

        self.menubutton_file.grid(row=row, column=column)
        column += 1

        # Menu: Edit
        self.menubutton_edit = Menubutton(frame, font=self.tkfont_dejavu_10,
                                          text="Edit", relief=RAISED,
                                          width=HeathUI.MENU_WIDTH,
                                          height=HeathUI.MENU_HEIGHT)
        self.menubutton_edit.menu = Menu(self.menubutton_edit, tearoff=0)
        self.menubutton_edit["menu"] = self.menubutton_edit.menu

        self.menubutton_edit.grid(row=row, column=column)
        column += 1


        self.menubutton_edit.menu.add_command(accelerator="h",
                                              label="Add hostname",
                                              underline=0,
                                              command=self.show_top)


        # Menu: Run
        # TODO
        self.menubutton_run = Menubutton(frame, font=self.tkfont_dejavu_10,
                                          text="Run", relief=RAISED,
                                          width=HeathUI.MENU_WIDTH,
                                          height=HeathUI.MENU_HEIGHT)
        self.menubutton_run.menu = Menu(self.menubutton_run, tearoff=0)
        self.menubutton_run["menu"] = self.menubutton_run.menu

        self.menubutton_run.grid(row=row, column=column)
        column += 1

        self.menubutton_run.menu.add_command(accelerator="m", label="Macro1",
                                              underline=0,
                                              command=self.cmd_run_macro)

        # Menu: Tools
        # TODO

        # Menu: Help
        # TODO

        frame.grid(row=0, column=0, sticky='nw')

        pass

    def serve_data(self, sock):
        while True:
            try:
                data = sock.recv(4096)
                self.logger.info('{0:<15}: Recieved from {1} {2!r}'.format(
                                 self.__class__.__name__, sock.getpeername()[0],
                                 data))
                self.console.append(data)
            except select.error, e:
                self.logger.error('{0:<15}: select.error: {1}'.format(
                                  self.__class__.__name__, e))
                break

        self.logger.info('{0:<15}: Connection closed by peer, exit'.format(
                         self.__class__.__name__))
        sock.close()
        pass

    def serve_data_forever(self):
        self.logger.info('{0:<15}: Logging Server running'.format(
                         self.__class__.__name__))
        while self.server_run:
            conn, addr = self.server_data_sock.accept()
            self.logger.info('{0:<15}: Connected from {1}:{2}'.format(
                             self.__class__.__name__, addr[0], addr[1]))
            self.log_client_sock = conn
            th = threading.Thread(target=self.serve_data, args=(conn,))
            th.setName(self.serve_data.__name__)
            th.start()
            self.server_threads.append(th)

        self.logger.info('{0:<15}: Logging Server exit'.format(
                         self.__class__.__name__))
        pass

    def serve_cmd(self, sock):
        while True:
            cmd = self.cmdq.get()
            self.logger.info('{0:<15}: Sending cmd to {1} {2!r}'.format(
                             self.__class__.__name__, sock.getpeername()[0],
                             cmd))
            sock.sendall(pickle.dumps(cmd))
            self.cmdq.task_done()

        self.logger.info('{0:<15}: Connection closed by peer, exit'.format(
                         self.__class__.__name__))
        #sock.close()
        pass

    def serve_cmd_forever(self):
        self.logger.info('{0:<15}: Server running'.format(
                         self.__class__.__name__))
        while self.server_run:
            conn, addr = self.server_cmd_sock.accept()
            self.logger.info('Connected from: %s:%s' % (addr[0], addr[1]))
            th = threading.Thread(target=self.serve_cmd, args=(conn,))
            th.setName(self.serve_cmd.__name__)
            th.start()
            self.server_threads.append(th)

        # self.server_threads[0] is ourself
        for th in self.server_threads[1:]:
            th.join()
        self.logger.info('Server exit')
        pass

    def handler_key(self, event):
        self.logger.debug("{0:<15}: pressed {1!r} {2!r} {3}".format(
                          self.__class__.__name__,
                          event.keysym,
                          event.keycode,
                          event.char))

        if self.log_client_sock:
            self.log_client_sock.send(event.char.replace('\n', '\n\r'))

        return 'break'

    def cmd_connect(self):
        cmd = {'cmd' : 'connect',
               'hostname' : self.tkvars['entry_hostname'].get()}
        self.cmdq.put(cmd)
        self.logger.info('{0:<15}: Put cmd "{1}" in queue'.format(
                         self.__class__.__name__, cmd['cmd']))
        pass

    def cmd_load_eboot(self):
        cmd = {'cmd' : 'reload', 'content' : ''}
        self.cmdq.put(cmd)
        self.logger.info('{0:<15}: Put cmd "{1}" in queue'.format(
                         self.__class__.__name__, cmd['cmd']))
        pass

    def cmd_load_ios(self):
        cmd = {'cmd' : 'loadios',
                'url' : 'b ' + self.tkvars["entry_location"].get()
                             + self.tkvars["entry_image"].get()}
        self.cmdq.put(cmd)
        self.logger.info('{0:<15}: Put cmd "{1}" in queue'.format(
                         self.__class__.__name__, cmd['cmd']))
        pass

    def cmd_reload(self):
        self.logger.info('{0:<15}: Put cmd "reload" in queue'.format(
                         self.__class__.__name__))

        cmd = {'cmd' : 'reload', 'cli' : 'reload'}
        self.cmdq.put(cmd)
        self.logger.info('{0:<15}: Put cmd "{1}" in queue'.format(
                         self.__class__.__name__, cmd['cmd']))
        pass

    def cmd_exit(self):
        self.exit()

    def cmd_update_hostname(self, selection):
        self.logger.info('{0:<15}: Selection changed {1}'.format(
                         self.__class__.__name__, selection))
        pass

    def cmd_add_hostname(self):
        hostname = self.tkvars["new_hostname"].get()
        self.logger.info('{0:<15}: Add hostname {1!r}'.format(
                         self.__class__.__name__, hostname))
        if hostname:
            self.tkvars["entry_hostname_list"].append(hostname)
            self.optionmenu_hostname.option_clear()
            self.optionmenu_hostname.option_add(
                    self.tkvars["entry_hostname_list"][0],
                    self.tkvars["entry_hostname_list"])
            self.optionmenu_hostname.update()
            self.hide_top()
        pass

    def cmd_load_host(self):
        pass

    def quit(self):
        Tk.quit(self)
        pass

    def exit(self):
        '''Kill all server thread and exit
        '''

        self.server_run = False
        #self.server_threads[0].join() # it's daemon, and will not join
        try:
            # Error when there's no connection at this point
            self.server_cmd_sock.shutdown(socket.SHUT_RDWR)
            self.server_cmd_sock.close()
        except socket.error, e:
            pass

        self.quit()
        pass

    def cmd_run_macro(self, which=0):
        self.logger.info('{0:<15}: Run macro {1}'.format(
                         self.__class__.__name__, which))

        pass

    def test_console_frame(self):
        TEST_STR = "abcdefghijklmnopqrstuvwxyz0123456789"
        self.logger.info("Test console frame")
        try:
            while True:
                time.sleep(1)
                self.console.append(TEST_STR * 100)
                continue
                for i in range(10):
                    self.console.append(TEST_STR * 10)
        except MemoryError, e:
            self.logger.warn('MemoryError: %s' % e)
            sys.exit(-1)
        except OverflowError, e:
            self.logger.warn('OverflowError: %s' % e)
            sys.exit(-1)

class HeathUITest(TestCase):
    def setUp(self):
        self.heathui = HeathUI()
        self.heathui.title("Heath UI")

        self.th = threading.Thread(target=self.heathui.test_console_frame)

        pass

    def test_ui(self):
        self.th.daemon = True
        #self.th.start()
        self.heathui.mainloop()
        time.sleep(2)
        pass

    def list_tkfont_fonts(self):
        fonts = filter(lambda x: x[0] != "@", tkFont.families())
        for f in fonts:
            print f

    def runTest(self):
        #self.list_tkfont_fonts()

        self.test_ui()

        pass

    def tearDown(self):
        pass

def main():
    ts = TestSuite()
    ts.addTest(HeathUITest())
    TextTestRunner(verbosity=2).run(ts)

    pass

if __name__ == '__main__':
    main()

